package suncertify.gui;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.rmi.RemoteException;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;

import suncertify.application.ApplicationMode;
import suncertify.application.ClientApplication;
import suncertify.application.Log;
import suncertify.application.Strings;
import suncertify.contractors.exceptions.CloseContractorsException;
import suncertify.contractors.exceptions.ContractorFindException;
import suncertify.contractors.exceptions.ContractorUpdateException;
import suncertify.contractors.exceptions.ReadAllContractorsException;
import suncertify.gui.dialogs.BookingDialog;
import suncertify.gui.dialogs.ClientConnectionDialog;

/**
 * Provides the client application frame window that consists of the following user interface components:
 * <ol>
 * <li>Resizable window that shows the application's name in the title bar. The window is shown in the center of the
 * screen. The window is resizable and has a minimum size defined by {@link #JFRAME_MIN_WIDTH} and
 * {@link #JFRAME_MIN_HEIGHT} constants.</li>
 * <li>The application's file menu that contains the exit menu.</li>
 * <li>The application's toolbar that contains the book and unbook buttons.</li>
 * <li>The application's JTable that shows the list of contractors and the result of search commands.</li>
 * <li>The application's search panel that contains the name and location text fields, the search button and the refresh
 * button.</li>
 * </ol>
 * 
 * <p>
 * This class represents the view part in a MVC pattern. The class must be constructed with an implementation of the
 * {@link ClientApplicationFrameControllerInterface} that represents the controller part in a MVC pattern. The main
 * objective of this class is to host the view, the class creates all the components of the view like buttons, text
 * fields and tables, sets the layout of the components, assigns texts, tool tips, and mnemonic keys.
 * 
 * <p>
 * All user interactions with the application (like clicking the search button) fire the
 * {@link #actionPerformed(ActionEvent)} function. Each UI component that performs an action (e.g. the search button) is
 * assigned a unique string defined by the internal <code>Commands</code> enumerator. The
 * {@link #actionPerformed(ActionEvent)} function filters the action events based on the enumerator value and calls the
 * corresponding action function, e.g. {@link #doSearchNameLocationAction()} and {@link #doBookAction()}.
 * 
 * <p>
 * The action functions delegate the call to the corresponding controller function, for each user interaction there is a
 * corresponding controller method e.g. the
 * {@link ClientApplicationFrameControllerInterface#searchNameLocationContractors(String, String)} method is called when
 * the user clicks the search button. The controller is responsible for updating the data in the application frame. Any
 * exceptions raised by the controller are caught in the {@link #actionPerformed(ActionEvent)} function and are shown
 * using the {@link DesktopGui#showErrorMessage(String, String)}.
 * 
 * <p>
 * This frame is shown in conjunction with the {@link ClientConnectionDialog} and the
 * {@link ClientApplicationFrameControllerInterface} see the {@link ClientApplication#startApplication()} function for
 * details.
 * 
 */
public class ClientApplicationFrame extends JFrame implements ActionListener, WindowListener, MouseListener {

    /**
     * Enumeration of the possible actions performed by the JFrame GUI. Each enumeration represents a user interaction
     * e.g. <code>book</code> identifies the click of the tool bar book button.
     * 
     */
    private enum Commands {

	/**
	 * Represents a search by name and/or location action.
	 */
	SEARCH_NAME_LOCATION,

	/**
	 * Represents a contractor booking action.
	 */
	BOOK,

	/**
	 * Represents a contractor remove booking action.
	 */
	UNBOOK,

	/**
	 * Represents a clear search action.
	 */
	CLEAR_SEARCH,

	/**
	 * represents an application exit action.
	 */
	EXIT
    }

    /**
     * Automatic generated serialization version ID.
     */
    private static final long serialVersionUID = 5188540235634822520L;

    /**
     * Constant definition that defines the JFrame's initial width.
     */
    private final static int JFRAME_WIDTH = 620;

    /**
     * Constant definition that defines the JFrame's initial height.
     */
    private final static int JFRAME_HEIGHT = 400;

    /**
     * Constant definition that defines the JFrame's minimum width.
     */
    private final static int JFRAME_MIN_WIDTH = 600;

    /**
     * Constant definition that defines the JFrame's minimum height.
     */
    private final static int JFRAME_MIN_HEIGHT = 200;

    /**
     * Declares and constructs the name search text field.
     */
    private JTextField searchNameField = new JTextField(14);

    /**
     * Declares and constructs the location search text field.
     */
    private JTextField searchLocationField = new JTextField(14);

    /**
     * Declares the search button.
     */
    private JButton searchNameLocationButton;

    /**
     * Declares the clear search button.
     */
    private JButton clearSearchButton;

    /**
     * Declares the toolbar book button.
     */
    private JButton bookButton;

    /**
     * Declares the toolbar button that removes the booking from the selected contractor.
     */
    private JButton unbookButton;

    /**
     * Declares and creates the contractors JTable grid.
     */
    private JTable contractorsTable = new JTable();

    /**
     * Declares the {@link ApplicationMode} member variable. Allowed values are
     * {@link ApplicationMode#STANDALONE_CLIENT} and {@link ApplicationMode#NETWORK_CLIENT}
     */
    private ApplicationMode applicationMode;

    /**
     * Declares the JFrame controller interface. All actions performed by the JFrame are forwarded to this controller.
     */
    private ClientApplicationFrameControllerInterface frameController;

    /**
     * Constructs the class with the specified <code>frameController</code> parameter. The controller parameter is used
     * to execute all user interactions. The constructor:
     * <ol>
     * <li>Initializes the whole JFrame components. It shows the application's name and mode in the window title bar.</li>
     * <li>Creates the file menu with exit menu command.</li>
     * <li>Creates the application's toolbar that contains the book and unbook buttons.</li>
     * <li>Creates the scroll-able pane that hosts the {@link JTable}</li>
     * <li>Creates the search bottom panel that contains the name and location text fields, the search button and the
     * refresh button.</li>
     * <li>Shows the JFrame by setting its visibility to true</li>
     * </ol>
     * 
     * <p>
     * When the constructor returns the whole JFrame is disabled. After the class is populated with data then all GUI
     * components are enabled using the {@link #enableControls()}.
     * 
     * @param frameController
     *            The controller of this JFrame. The class relies on the controller to perform all the user
     *            interactions.
     */
    public ClientApplicationFrame(ClientApplicationFrameControllerInterface frameController) {

	// save the controller reference and pass the reference of the view (JTable) to the controller.
	this.frameController = frameController;
	this.frameController.setViewControl(this.contractorsTable);

	this.applicationMode = this.frameController.getApplicationMode();

	this.addWindowListener(this);

	if (this.applicationMode == ApplicationMode.STANDALONE_CLIENT) {
	    this.setTitle(Strings.CAP_CLIENT_ALONE_TITLE);
	} else if (this.applicationMode == ApplicationMode.NETWORK_CLIENT) {
	    this.setTitle(Strings.CAP_CLIENT_NETWORK_TITLE);
	}

	this.createMenu();

	this.setLayout(new BorderLayout());
	this.add(this.createToolBar(), BorderLayout.NORTH);

	JScrollPane tableScroll = new JScrollPane(contractorsTable);
	this.add(tableScroll, BorderLayout.CENTER);

	// Add the bottom panel to the main window
	this.add(this.createSearchNameLocationPanel(), BorderLayout.SOUTH);

	// Set table properties
	this.contractorsTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	this.contractorsTable.setAutoResizeMode(JTable.AUTO_RESIZE_ALL_COLUMNS);
	this.contractorsTable.addMouseListener(this);

	this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	this.pack();
	this.setSize(JFRAME_WIDTH, JFRAME_HEIGHT);
	this.setMinimumSize(new Dimension(JFRAME_MIN_WIDTH, JFRAME_MIN_HEIGHT));
	this.centerOnScreen();
	this.setEnabled(false);
	this.setVisible(true);

	this.getRootPane().setDefaultButton(this.searchNameLocationButton);

	Log.logInfo("ClientApplicationFrame constructor finished successfully.");
    }

    /**
     * Enables the JFrame components. Usually called after the JFrame is populated with data.
     */
    public void enableControls() {

	this.setEnabled(true);
    }

    /**
     * Centers the JFrame in the center of the screen.
     */
    private void centerOnScreen() {

	Point point = DesktopGui.getCenterPoint(this.getWidth(), this.getHeight());
	this.setLocation(point);
    }

    /**
     * Creates the client application's menu. The menu contains the quit command. The quit menu is assigned the
     * {@link Commands#EXIT} enumeration. Clicking the exit menu fires the {@link #actionPerformed(ActionEvent)}
     * function.
     */
    private void createMenu() {

	JMenuBar menuBar = new JMenuBar();
	JMenu fileMenu = new JMenu(Strings.CAP_FILE);
	fileMenu.setMnemonic(KeyEvent.VK_F);

	JMenuItem quitMenuItem = new JMenuItem(Strings.CAP_QUIT);
	quitMenuItem.addActionListener(this);
	quitMenuItem.setActionCommand(Commands.EXIT.toString());
	quitMenuItem.setMnemonic(KeyEvent.VK_Q);

	fileMenu.add(quitMenuItem);

	menuBar.add(fileMenu);

	this.setJMenuBar(menuBar);
    }

    /**
     * Creates the client application's toolbar. The toolbar contains the book and unbook buttons. The toolbar appears
     * by default below the file menu. The toolbar can be dragged and docked to the bottom, left and right or can be
     * left floating. The tool bar buttons are assigned the {@link Commands#BOOK} and {@link Commands#UNBOOK}
     * enumeration as action command, clicking the any of the buttons fires the {@link #actionPerformed(ActionEvent)}
     * function.
     * 
     * @return The applications toolbar.
     * 
     */
    private JToolBar createToolBar() {

	// Setup booking buttons
	bookButton = new JButton(Strings.CAP_BOOK);
	bookButton.addActionListener(this);
	bookButton.setActionCommand(Commands.BOOK.toString());
	bookButton.setRequestFocusEnabled(false);
	bookButton.setMnemonic(KeyEvent.VK_B);
	bookButton.setToolTipText(Strings.TIP_BOOK_CONTRACTOR);

	unbookButton = new JButton(Strings.CAP_UNBOOK);
	unbookButton.addActionListener(this);
	unbookButton.setActionCommand(Commands.UNBOOK.toString());
	unbookButton.setRequestFocusEnabled(false);
	unbookButton.setMnemonic(KeyEvent.VK_C);
	unbookButton.setToolTipText(Strings.TIP_UNBOOK_CONTRACTOR);

	JToolBar toolbar = new JToolBar(JToolBar.HORIZONTAL);
	toolbar.add(bookButton);
	toolbar.add(unbookButton);

	toolbar.setFloatable(true);

	return toolbar;
    }

    /**
     * Creates the application's search panel that consist of the name and location text fields and labels, the search
     * button and the refresh button. The contents of the panel are aligned to the left.
     * 
     * @return The search panel.
     */
    private JPanel createSearchNameLocationPanel() {

	JLabel searchNameLabel = new JLabel(Strings.CAP_SEARCH_FOR_NAME);
	searchNameLabel.setDisplayedMnemonic(KeyEvent.VK_M);
	searchNameLabel.setLabelFor(this.searchNameField);

	JLabel searchAndOrLabel = new JLabel(Strings.CAP_LOCATION);
	searchAndOrLabel.setDisplayedMnemonic(KeyEvent.VK_T);
	searchAndOrLabel.setLabelFor(this.searchLocationField);

	searchNameLocationButton = new JButton(Strings.CAP_SEARCH);
	searchNameLocationButton.addActionListener(this);
	searchNameLocationButton.setActionCommand(Commands.SEARCH_NAME_LOCATION.toString());
	searchNameLocationButton.setMnemonic(KeyEvent.VK_S);
	searchNameLocationButton.setToolTipText(Strings.TIP_SEARCH);

	clearSearchButton = new JButton(Strings.CAP_REFRESH);
	clearSearchButton.addActionListener(this);
	clearSearchButton.setActionCommand(Commands.CLEAR_SEARCH.toString());
	clearSearchButton.setMnemonic(KeyEvent.VK_R);
	clearSearchButton.setToolTipText(Strings.TIP_CLEAR_SEARCH);

	searchNameField.setToolTipText(Strings.TIP_NAME_FIELD_SEARCH);
	searchLocationField.setToolTipText(Strings.TIP_CITY_FIELD_SEARCH);

	JPanel searchNameLocationPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
	searchNameLocationPanel.add(searchNameLabel);
	searchNameLocationPanel.add(searchNameField);
	searchNameLocationPanel.add(searchAndOrLabel);
	searchNameLocationPanel.add(searchLocationField);
	searchNameLocationPanel.add(searchNameLocationButton);
	searchNameLocationPanel.add(clearSearchButton);

	return searchNameLocationPanel;
    }

    /**
     * Called when the user clicks the search button, the refresh button, the book/unbook button or exit menu. The
     * performed action is identified by the <code>e.getActionCommand()</code> string value that is assigned to each
     * component that performs GUI actions like menu items and buttons.
     * <p>
     * The function calls the corresponding function in the frame controller and handles all thrown exceptions by
     * showing an error message dialog that informs the user about the error.
     * 
     * @param e
     *            The action event object.
     * 
     * @see java.awt.event.ActionListener#actionPerformed(java.awt.event.ActionEvent)
     */
    public void actionPerformed(ActionEvent e) {

	try {
	    if (e.getActionCommand().equalsIgnoreCase(Commands.CLEAR_SEARCH.toString())) {

		this.doClearSearchAction();

	    } else if (e.getActionCommand().equalsIgnoreCase(Commands.SEARCH_NAME_LOCATION.toString())) {

		this.doSearchNameLocationAction();

	    } else if (e.getActionCommand().equalsIgnoreCase(Commands.BOOK.toString())) {

		this.doBookAction();

	    } else if (e.getActionCommand().equalsIgnoreCase(Commands.UNBOOK.toString())) {

		this.doUnbookAction();

	    } else if (e.getActionCommand().equalsIgnoreCase(Commands.EXIT.toString())) {
		this.doTerminateApplication();
	    }
	} catch (ReadAllContractorsException ex) {
	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught ReadAllContractorsException.");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CONTRACTORS_READ_ERROR);

	} catch (ContractorFindException ex) {
	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught ContractorFindException.");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CONTRACTORS_FIND_ERROR);
	} catch (IllegalStateException ex) {

	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught IllegalStateException.");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_SERVER_NOT_RUNNING_MESSAGE);

	} catch (ContractorUpdateException ex) {

	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught ContractorUpdateException.");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_CONTRACTOR_UPDATE_ERROR2);

	} catch (RemoteException ex) {
	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught RemoteException.");
	    DesktopGui.showErrorMessage(Strings.CAP_ERROR, Strings.MSG_REMOTE_CONNECTION_ERROR);
	} catch (Exception ex) {
	    Log.logInfo("ClientApplicationFrame.actionPerformed() caught Exception.");
	    DesktopGui.showPerformedActionErrorMessage(ex);
	}
    }

    /**
     * Closes the applications database file when the application terminates. The database file is only closed when the
     * application runs in the {@link ApplicationMode#STANDALONE_CLIENT}.
     */
    private void closeDatabaseFile() {

	Log.logInfo("Entering client application windowClosing.");

	try {
	    if (this.applicationMode == ApplicationMode.STANDALONE_CLIENT) {
		this.frameController.closeDatabaseFile();
		Log.logInfo("Client application called close database file successfully.");
	    }
	} catch (CloseContractorsException e1) {
	    Log.logInfo("close database file call caused exception:" + e1.getMessage());
	} catch (Exception e1) {
	    Log.logInfo("close database file call caused exception:" + e1.getMessage());
	}
    }

    /**
     * Resets the search fields in the search panel. The name and location text fields are cleared and the table view is
     * populated with all contractors.
     * 
     * @throws Exception
     *             Thrown if any error occurs while loading the contractors.
     */
    private void doClearSearchAction() throws Exception {

	searchNameField.setText("");
	searchLocationField.setText("");

	this.frameController.getAllContractors();
    }

    /**
     * Called when the search button is clicked. The name and location text values are trimmed and passed to the
     * {@link ClientApplicationFrameControllerInterface#searchNameLocationContractors(String, String)} method is called
     * on the frame's controller. The contractors JTable is cleared and populated with the contractors that match the
     * search criteria.
     * 
     * @throws Exception
     *             Thrown if any error occurs while executing
     *             {@link ClientApplicationFrameControllerInterface#searchNameLocationContractors(String, String)}.
     */
    private void doSearchNameLocationAction() throws Exception {

	String searchName = searchNameField.getText().trim();
	String searchLocation = searchLocationField.getText().trim();

	this.frameController.searchNameLocationContractors(searchName, searchLocation);
    }

    /**
     * Called when the book button in the applications toolbar is clicked. This function simply calls the corresponding
     * controller function {@link ClientApplicationFrameControllerInterface#bookContractor(Object)}. This function shows
     * the {@link BookingDialog} that allows the user to enter the contractors owner.
     * 
     * @throws Exception
     *             Thrown if any exceptions are thrown by
     *             {@link ClientApplicationFrameControllerInterface#bookContractor(Object)}.
     */
    private void doBookAction() throws Exception {

	this.frameController.bookContractor(this);
    }

    /**
     * Called when the unbook button in the application's toolbar is clicked. This function simply calls the
     * corresponding controller function {@link ClientApplicationFrameControllerInterface#unbookContractor()}.
     * 
     * @throws Exception
     *             Thrown by {@link ClientApplicationFrameControllerInterface#unbookContractor()}
     */
    private void doUnbookAction() throws Exception {

	this.frameController.unbookContractor();
    }

    /**
     * Called when the exit menu item is clicked. Calls the corresponding frame controller function
     * {@link ClientApplicationFrameControllerInterface#terminateApplication()} which terminates the JVM.
     */
    private void doTerminateApplication() {

	this.closeDatabaseFile();

	this.frameController.terminateApplication();
    }

    /**
     * Called when the JFrame window is closed. In the Microsoft Windows operating system this function is called if the
     * window is closed with (x) button in the windows title bar. This function calls the {@link #closeDatabaseFile()}
     * 
     * @param e
     *            See {@link WindowEvent}.
     * 
     */
    public void windowClosing(WindowEvent e) {

	this.closeDatabaseFile();
    }

    /**
     * Empty implementation for {@link WindowListener#windowActivated(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowActivated(WindowEvent)}
     * 
     */
    public void windowActivated(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowClosed(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowClosed(WindowEvent)}
     * 
     */
    public void windowClosed(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowDeactivated(WindowEvent)}. This function does not contain
     * any code.
     * 
     * @param e
     *            See {@link WindowListener#windowDeactivated(WindowEvent)}
     * 
     */
    public void windowDeactivated(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowDeiconified(WindowEvent)}. This function does not contain
     * any code.
     * 
     * @param e
     *            See {@link WindowListener#windowDeiconified(WindowEvent)}
     * 
     */
    public void windowDeiconified(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowIconified(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowIconified(WindowEvent)}
     * 
     */
    public void windowIconified(WindowEvent e) {

    }

    /**
     * Empty implementation for {@link WindowListener#windowOpened(WindowEvent)}. This function does not contain any
     * code.
     * 
     * @param e
     *            See {@link WindowListener#windowOpened(WindowEvent)}
     * 
     */
    public void windowOpened(WindowEvent e) {

    }

    /**
     * Called by Swing when the mouse is clicked in the {@link JTable}. If the mouse is not double clicked then the
     * function returns immediately. If the mouse is double clicked then the {@link #actionPerformed(ActionEvent)}
     * function is called with an {@link ActionEvent} that corresponds to a {@link ActionCommands#BOOK}.
     * 
     * @param e
     *            See {@link MouseEvent} and {@link MouseListener}
     */
    public void mouseClicked(MouseEvent e) {

	if (e.getClickCount() != 2) {
	    return;
	}
	ActionEvent actionEvent = new ActionEvent(this.contractorsTable, 0, ActionCommands.BOOK.toString());
	this.actionPerformed(actionEvent);
    }

    /**
     * Empty implementation for {@link MouseListener#mouseEntered(MouseEvent)}. This function contains no code.
     * 
     * @param e
     *            See {@link MouseListener#mouseEntered(MouseEvent)}.
     * 
     */
    public void mouseEntered(MouseEvent e) {

    }

    /**
     * Empty implementation for {@link MouseListener#mouseExited(MouseEvent)}. This function contains no code.
     * 
     * @param e
     *            See {@link MouseListener#mouseExited(MouseEvent)}.
     * 
     */
    public void mouseExited(MouseEvent e) {

    }

    /**
     * Empty implementation for {@link MouseListener#mousePressed(MouseEvent)}. This function contains no code.
     * 
     * @param e
     *            See {@link MouseListener#mousePressed(MouseEvent)}.
     * 
     */
    public void mousePressed(MouseEvent e) {

    }

    /**
     * Empty implementation for {@link MouseListener#mouseReleased(MouseEvent)}. This function contains no code.
     * 
     * @param e
     *            See {@link MouseListener#mouseReleased(MouseEvent)}.
     * 
     */
    public void mouseReleased(MouseEvent e) {

    }
}
